﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Linq;
using Laedit.Extensions.CodeDomExtensions;

namespace AppConfigCodeGenerator
{
    /// <summary>
    /// The only purpose of this class is to generates the code for AppConfig Code Generator
    /// But this class can use as a third-party by an application
    /// </summary>
    public static class CodeGenerator
    {
        #region Constants & read-only declarations

        #region Types
        private readonly static CodeTypeReference TYPE_BOOLEAN = new CodeTypeReference("System.Boolean");
        private readonly static CodeTypeReference TYPE_BOOLEAN_ARRAY = new CodeTypeReference("System.Boolean", 1);
        private readonly static CodeTypeReference TYPE_CHAR = new CodeTypeReference("System.Char");
        private readonly static CodeTypeReference TYPE_CHAR_ARRAY = new CodeTypeReference("System.Char", 1);
        private readonly static CodeTypeReference TYPE_CONFIGURATION = new CodeTypeReference("System.Configuration.Configuration");
        private readonly static CodeTypeReference TYPE_CONFIGURATIONMANAGER = new CodeTypeReference("System.Configuration.ConfigurationManager");
        private readonly static CodeTypeReference TYPE_CONFIGURATIONSAVEMODE = new CodeTypeReference("System.Configuration.ConfigurationSaveMode");
        private readonly static CodeTypeReference TYPE_CONFIGURATIONUSERLEVEL = new CodeTypeReference("System.Configuration.ConfigurationUserLevel");
        private readonly static CodeTypeReference TYPE_CONNECTIONSTRINGSETTINGS = new CodeTypeReference("System.Configuration.ConnectionStringSettings");
        private readonly static CodeTypeReference TYPE_DATETIME = new CodeTypeReference("System.DateTime");
        private readonly static CodeTypeReference TYPE_DATETIME_ARRAY = new CodeTypeReference("System.DateTime", 1);
        private readonly static CodeTypeReference TYPE_DECIMAL = new CodeTypeReference("System.Decimal");
        private readonly static CodeTypeReference TYPE_DECIMAL_ARRAY = new CodeTypeReference("System.Decimal", 1);
        private readonly static CodeTypeReference TYPE_DOUBLE = new CodeTypeReference("System.Double");
        private readonly static CodeTypeReference TYPE_DOUBLE_ARRAY = new CodeTypeReference("System.Double", 1);
        private readonly static CodeTypeReference TYPE_INT32 = new CodeTypeReference("System.Int32");
        private readonly static CodeTypeReference TYPE_INT32_ARRAY = new CodeTypeReference("System.Int32", 1);
        private readonly static CodeTypeReference TYPE_OBJECT = new CodeTypeReference("System.Object");
        private readonly static CodeTypeReference TYPE_SINGLE = new CodeTypeReference("System.Single");
        private readonly static CodeTypeReference TYPE_SINGLE_ARRAY = new CodeTypeReference("System.Single", 1);
        private readonly static CodeTypeReference TYPE_STRING = new CodeTypeReference("System.String");
        private readonly static CodeTypeReference TYPE_STRING_ARRAY = new CodeTypeReference("System.String", 1);
        private readonly static CodeTypeReference TYPE_UINT32 = new CodeTypeReference("System.UInt32");
        private readonly static CodeTypeReference TYPE_UINT32_ARRAY = new CodeTypeReference("System.UInt32", 1);
        private readonly static CodeTypeReference TYPE_CONVERT = new CodeTypeReference("System.Convert");
        private readonly static CodeTypeReference TYPE_T = new CodeTypeReference("T", CodeTypeReferenceOptions.GenericTypeParameter);
        private readonly static CodeTypeReference TYPE_T_ARRAY = new CodeTypeReference("T", 1);
        private readonly static CodeTypeReference TYPE_IENUMERATOR = new CodeTypeReference("System.Collections.IEnumerator");
        #endregion Types

        #region Fields
        private readonly static CodeFieldReferenceExpression FIELD_STRING_EMPTY = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(TYPE_STRING), "Empty");
        #endregion Fields

        #region Properties
        private const String PROPERTY_APPSETTINGS_SETTINGS = "Settings";
        private const String PROPERTY_CONFIGURATIONMANAGER_APPSETTINGS = "AppSettings";
        private readonly static CodePropertyReferenceExpression PROPERTY_CONFIGURATIONMANAGER_CONNECTIONSTRINGS = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(TYPE_CONFIGURATIONMANAGER), "ConnectionStrings");
        private readonly static CodePropertyReferenceExpression PROPERTY_CONFIGURATIONSAVEMODE_MODIFIED = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(TYPE_CONFIGURATIONSAVEMODE), "Modified");
        private readonly static CodePropertyReferenceExpression PROPERTY_CONFIGURATIONUSERLEVEL_NONE = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(TYPE_CONFIGURATIONUSERLEVEL), "None");
        private const String PROPERTY_SETTINGS_VALUE = "Value";
        private const String PROPERTY_ARRAY_LENGTH = "Length";
        private const String PROPERTY_IENUMERABLE_CURRENT = "Current";
        #endregion Properties

        #region Methods
        private const String METHOD_STRING_ISNULLOREMPTY = "IsNullOrEmpty";
        private const String METHOD_STRING_SPLIT = "Split";
        private const String METHOD_STRING_REMOVE = "Remove";
        private const String METHOD_GENERATEDCONFIGURATION_CONVERT_ARRAY = "ConvertArray";
        private const String METHOD_GENERATEDCONFIGURATION_SAVE = "Save";
        private const String METHOD_GENERATEDCONFIGURATION_SAVE_APPSETTING = "SaveAppSetting";
        private const String METHOD_CONFIGURATION_SAVE = "Save";
        private const String METHOD_CONFIGURATIONMANAGER_OPENEXECONFIGURATION = "OpenExeConfiguration";
        private const String METHOD_CONFIGURATIONMANAGER_REFRESHSECTION = "RefreshSection";
        private const String METHOD_OBJECT_TOSTRING = "ToString";
        private const String METHOD_CONVERT_TOBOOLEAN = "ToBoolean";
        private const String METHOD_CONVERT_TOCHAR = "ToChar";
        private const String METHOD_CONVERT_TODATETIME = "ToDateTime";
        private const String METHOD_CONVERT_TODECIMAL = "ToDecimal";
        private const String METHOD_CONVERT_TODOUBLE = "ToDouble";
        private const String METHOD_CONVERT_TOINT32 = "ToInt32";
        private const String METHOD_CONVERT_TOUINT32 = "ToUInt32";
        private const String METHOD_CONVERT_TOSINGLE = "ToSingle";
        private const String METHOD_CONVERT_CHANGETYPE = "ChangeType";
        private const String METHOD_IENUMERATOR_GETENUMERATOR = "GetEnumerator";
        private const String METHOD_IENUMERATOR_MOVENEXT = "MoveNext";
        #endregion Methods

        #endregion Constants & read-only declarations


        #region Fields

        private static Boolean includeSaveAppSetting = false;
        private static Boolean includeSave = false;
        private static Boolean includeSaveIEnumerable = false;
        private static Boolean includeConvertArray = false;

        #endregion Fields


        #region Public methods

        /// <summary>
        /// Generates the specified input filename.
        /// </summary>
        /// <param name="projectNamespace">The project namespace.</param>
        /// <param name="applicationName">Name of the application.</param>
        /// <param name="language">The language.</param>
        public static String Generate(String projectNamespace, String applicationName, AvailableLanguage language, String inputFilePath)
        {
            XDocument xdoc = XDocument.Load(inputFilePath);
            XElement root = xdoc.Root;

            if (root != null)
            {
                // Get the appSettings section
                IEnumerable<XElement> appSettings = GetConfigSectionElements(root, "appSettings");

                // Get the connectionStrings section
                IEnumerable<XElement> connectionStrings = GetConfigSectionElements(root, "connectionStrings");

                // Get the CodeDomProvider for the desired language
                CodeDomProvider provider = GetProvider(language);
                // Build the CodeDom graph
                CodeCompileUnit compileUnit = BuildConfigurationGraph(projectNamespace, Path.GetFileNameWithoutExtension(applicationName), appSettings, connectionStrings);
                // Generates the source-code from the ComDom graph
                return GenerateCode(provider, compileUnit, Path.GetFileNameWithoutExtension(applicationName));

            }
            else
            {
                return "Error when parsing the .config file";
            }
        }


        #endregion Public methods


        #region Private methods

        /// <summary>
        /// Gets the config section elements.
        /// </summary>
        /// <param name="xdocument">The xdocument.</param>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns></returns>
        internal static IEnumerable<XElement> GetConfigSectionElements(XElement xdocument, String sectionName)
        {
            XElement section = xdocument.Element(sectionName);
            IEnumerable<XElement> sectionElements = new List<XElement>();
            if (section != null && section.HasElements)
            {
                sectionElements = section.Elements();
            }

            return sectionElements;
        }

        /// <summary>
        /// Builds the configuration graph.
        /// </summary>
        /// <param name="projectNamespace">The project namespace.</param>
        /// <param name="applicationName">Name of the application.</param>
        /// <param name="configuration">The configuration.</param>
        /// <returns></returns>
        private static CodeCompileUnit BuildConfigurationGraph(String projectNamespace, String applicationName, IEnumerable<XElement> appSettings, IEnumerable<XElement> connectionStrings)
        {
            // Variables initialization
            includeSaveAppSetting = false;
            includeSave = false;
            includeSaveIEnumerable = false;
            includeConvertArray = false;

            // Create a new CodeCompileUnit to contain 
            // the program graph.
            CodeCompileUnit compileUnit = new CodeCompileUnit();

            // Namespace creation
            CodeNamespace genNamespace = new CodeNamespace((String.IsNullOrEmpty(projectNamespace) ? applicationName : projectNamespace) + ".Configuration");
            compileUnit.Namespaces.Add(genNamespace);

            genNamespace.AddComment("--------------------------------------------------------------------------------------------"
                                        , "<auto-generatedInfo>"
                                        , "\t   This code was generated by AppConfig Code Generator (http://appconfigcodegen.codeplex.com)"
                                        , "\t   AppConfig Code Generator was written by Jérémie Bertrand and is under Apache V2.0 License"
                                        , String.Empty
                                        , "\t   This code contains class defined from the contents of the configuration (.config) file"
                                        , String.Empty
                                        , "\t   Generated: " + DateTime.Now.ToString()
                                        , "</auto-generatedInfo>"
                                        , "--------------------------------------------------------------------------------------------");

            // Project configuration class creation
            CodeTypeDeclaration configurationClass = CreateConfigurationClass(applicationName);
            genNamespace.Types.Add(configurationClass);


            // AppSettings properties
            foreach (XElement appSetting in appSettings)
            {
                configurationClass.Members.Add(CreateAppSettingProperty(appSetting, configurationClass.Name));
            }


            // ConnectionStrings properties
            foreach (XElement connectionString in connectionStrings)
            {
                configurationClass.Members.Add(CreateConnectionStringProperty(connectionString));
            }


            if (includeConvertArray)
            {
                configurationClass.Members.Add(CreateConvertArrayMethod());
            }

            if (includeSave)
            {
                // Save method creation
                configurationClass.Members.Add(CreateSaveMethod(configurationClass.Name));
            }

            if (includeSaveIEnumerable)
            {
                configurationClass.Members.Add(CreateSaveIEnumarableMethod(configurationClass.Name));
            }

            if (includeSaveAppSetting)
            {
                configurationClass.Members.Add(CreateSaveAppSettingMethod());
            }

            return compileUnit;
        }

        /// <summary>
        /// Creates the configuration class.
        /// </summary>
        /// <param name="applicationName">Name of the application.</param>
        /// <returns></returns>
        private static CodeTypeDeclaration CreateConfigurationClass(String applicationName)
        {
            CodeTypeDeclaration configurationClass = new CodeTypeDeclaration(applicationName + "Configuration");
            configurationClass.IsClass = true;
            configurationClass.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;

            configurationClass.AddDocComment("<summary>"
                                            , "Provide an easy and safe access to the AppSettings and ConnectionStrings"
                                            , "This class needs a reference from System.Configuration"
                                            , "</summary>");

            GeneratedCodeAttribute gca = new GeneratedCodeAttribute("AppConfig Code Generator", Assembly.GetExecutingAssembly().GetName().Version.ToString());
            configurationClass.CustomAttributes.Add(new CodeAttributeDeclaration(gca.GetType().FullName, new CodeAttributeArgument(new CodePrimitiveExpression(gca.Tool)), new CodeAttributeArgument(new CodePrimitiveExpression(gca.Version))));
            configurationClass.IsPartial = true;

            return configurationClass;
        }

        /// <summary>
        /// Creates the app setting property.
        /// </summary>
        /// <param name="xmlAppSetting">The XML app setting.</param>
        /// <param name="ownerClassName">Name of the owner class.</param>
        /// <returns></returns>
        private static CodeMemberProperty CreateAppSettingProperty(XElement xmlAppSetting, String ownerClassName)
        {
            CodeMemberProperty appSettingProperty = new CodeMemberProperty();

            String key = xmlAppSetting.Attribute("key").Value;

            appSettingProperty.AddDocComment("<summary>"
                                            , "Gets and sets the AppSetting " + key
                                            , "</summary>"
                                            , "<value>"
                                            , "The " + key + " value"
                                            , "</value>");

            // Get the name of the property (name of the appSettings with the first letter upper)
            appSettingProperty.Name = key[0].ToString().ToUpper();
            for (int i = 1; i < key.Length; i++)
            {
                appSettingProperty.Name += key[i];
            }

            // Get the type attribute(default String)
            XAttribute xmlType = xmlAppSetting.Attribute("type");
            Boolean isArray = false;
            if (xmlType != null)
            {
                isArray = IsArray(xmlType.Value);
                if (isArray)
                {
                    xmlType.Value = xmlType.Value.Replace("[]", String.Empty);
                }
                appSettingProperty.Type = GetTypeReference(xmlType.Value, isArray);
            }
            else
            {
                appSettingProperty.Type = TYPE_STRING;
            }

            // Set the attributes of the property (public static)
            appSettingProperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            // The property has a get and a set
            appSettingProperty.HasGet = true;
            appSettingProperty.HasSet = true;

            // Get the default attribute
            XAttribute xmlDefault = xmlAppSetting.Attribute("default");

            Char separator = ';';
            if (xmlAppSetting.Attribute("separator") != null)
            {
                String strSeparator = xmlAppSetting.Attribute("separator").Value;
                if (strSeparator != String.Empty)
                {
                    separator = strSeparator[0];
                }
            }

            // Create the get statements
            appSettingProperty.GetStatements.AddRange(CreateAppSettingPropertyGetStatements(key, (xmlType == null) ? null : xmlType.Value, isArray, separator, (xmlDefault == null) ? null : xmlDefault.Value, ownerClassName));

            if (isArray)
            {
                // Code: [ApplicationName]Configuration.Save(propertyName, value, separator);
                appSettingProperty.SetStatements.Add(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_SAVE, new CodePrimitiveExpression(key), new CodePropertySetValueReferenceExpression(), new CodePrimitiveExpression(separator)));
                includeSaveIEnumerable = true;
            }
            else
            {
                // Code: [ApplicationName]Configuration.Save(propertyName, value);
                appSettingProperty.SetStatements.Add(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_SAVE, new CodePrimitiveExpression(key), new CodePropertySetValueReferenceExpression()));
                includeSave = true;
            }

            return appSettingProperty;
        }

        /// <summary>
        /// Determines whether the specified type is array.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is array; otherwise, <c>false</c>.
        /// </returns>
        private static Boolean IsArray(String type)
        {
            return type.EndsWith("[]");
        }

        /// <summary>
        /// Creates the app setting property get statements.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="xmlType">Type of the XML.</param>
        /// <param name="xmlDefault">The XML default.</param>
        /// <returns></returns>
        private static CodeStatement[] CreateAppSettingPropertyGetStatements(String key, String type, Boolean isArray, Char separator, String appSettingDefault, String ownerClassName)
        {
            List<CodeStatement> getStatements = new List<CodeStatement>();

            if (isArray)
            {
                type = type.Replace("[]", String.Empty);
                getStatements = CreateAppSettingPropertyGetStatementsArray(key, type, separator, appSettingDefault, ownerClassName);
            }
            else
            {
                getStatements = CreateAppSettingPropertyGetStatementsSimple(key, type, appSettingDefault);
            }

            return getStatements.ToArray();
        } // end function CreateAppSettingPropertyGetStatements

        /// <summary>
        /// Creates the app setting property get statements simple.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="type">The type.</param>
        /// <param name="appSettingDefault">The app setting default.</param>
        /// <returns></returns>
        private static List<CodeStatement> CreateAppSettingPropertyGetStatementsSimple(String key, String type, String appSettingDefault)
        {
            List<CodeStatement> getStatements = new List<CodeStatement>();

            // If the appSettings hasn't the default attribute
            if (appSettingDefault == null)
            {
                // But it has a defined type
                if (type != null && type != "string")
                {
                    // Code: return Convert.To[definedType](ConfigurationManager.AppSettings[appSettingsName]);
                    getStatements.Add(new CodeMethodReturnStatement(GetConvertToMethod(type, GetConfigurationManagerAppSettingsExpression(key))));
                }
                // It hasn't a defined type
                else
                {
                    // Code: return ConfigurationManager.AppSettings[appSettingsName];
                    getStatements.Add(new CodeMethodReturnStatement(GetConfigurationManagerAppSettingsExpression(key)));
                }
            }
            // If the appSettings has the default attribute
            else
            {
                getStatements = CreateAppSettingPropertyGetStatementsSimpleWithDefault(key, type, appSettingDefault);
            }

            return getStatements;
        }

        /// <summary>
        /// Creates the app setting property get statements with default.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="type">The type.</param>
        /// <param name="appSettingDefault">The app setting default.</param>
        /// <param name="getStatements">The get statements.</param>
        private static List<CodeStatement> CreateAppSettingPropertyGetStatementsSimpleWithDefault(String key, String type, String appSettingDefault)
        {
            List<CodeStatement> getStatements = new List<CodeStatement>();

            String appSettingVariableName = "appSetting";
            // and a defined type
            if (type != null && type != "string")
            {
                /* Code: 
                 * String appSetting = ConfigurationManager.AppSettings[appSettingsName];
                 * if(String.IsNullOrEmpty(appSetting)
                 * {
                 *     return default(definedType); or return Convert.To[definedType](definedDefault);
                 * }
                 * else
                 * {
                 *     return Convert.To[definedType](appSetting);
                 * }
                 * */
                getStatements.Add(new CodeVariableDeclarationStatement(TYPE_STRING, appSettingVariableName, GetConfigurationManagerAppSettingsExpression(key)));

                getStatements.Add(new CodeConditionStatement(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_STRING), METHOD_STRING_ISNULLOREMPTY, new CodeVariableReferenceExpression(appSettingVariableName))
                                                ,
                                            new CodeStatement[] 
                                            { 
                                                ((appSettingDefault == String.Empty)?
                                                    new CodeMethodReturnStatement(new CodeDefaultValueExpression(GetTypeReference(type, false)))
                                                :
                                                    new CodeMethodReturnStatement(GetConvertToMethod(type, new CodePrimitiveExpression(appSettingDefault))) 
                                                )
                                            }
                                                ,
                                            new CodeStatement[] 
                                                { 
                                                    new CodeMethodReturnStatement(GetConvertToMethod(type, new CodeVariableReferenceExpression(appSettingVariableName)))
                                                }
                                                ));
            }
            // and hasn't a defined type
            else
            {
                /* Code: 
                 * String appSetting = ConfigurationManager.AppSettings[appSettingsName];
                 * if(String.IsNullOrEmpty(appSetting)
                 * {
                 *     return String.Empty; or return "definedDefault";
                 * }
                 * else
                 * {
                 *     return appSetting;
                 * }
                 * */
                getStatements.Add(new CodeVariableDeclarationStatement(TYPE_STRING, appSettingVariableName, GetConfigurationManagerAppSettingsExpression(key)));

                getStatements.Add(new CodeConditionStatement(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_STRING), METHOD_STRING_ISNULLOREMPTY, new CodeVariableReferenceExpression(appSettingVariableName))
                                                ,
                                            new CodeStatement[] 
                                            { 
                                                ((appSettingDefault == String.Empty)?
                                                    new CodeMethodReturnStatement(FIELD_STRING_EMPTY)
                                                :
                                                    new CodeMethodReturnStatement(new CodePrimitiveExpression(appSettingDefault)) 
                                                )
                                            }
                                                ,
                                            new CodeStatement[] { new CodeMethodReturnStatement(new CodeVariableReferenceExpression(appSettingVariableName)) }
                                                ));
            }

            return getStatements;
        }

        /// <summary>
        /// Creates the app setting property get statements simple.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="type">The type.</param>
        /// <param name="appSettingDefault">The app setting default.</param>
        /// <returns></returns>
        private static List<CodeStatement> CreateAppSettingPropertyGetStatementsArray(String key, String type, Char separator, String appSettingDefault, String ownerClassName)
        {
            List<CodeStatement> getStatements = new List<CodeStatement>();

            // If the appSettings hasn't the default attribute
            if (appSettingDefault == null)
            {
                // But it has a defined type
                if (type != null && type.ToLower() != "string")
                {
                    includeConvertArray = true;

                    // Code: return ConvertArray<definedType>(ConfigurationManager.AppSettings[key].Split(separator));
                    CodeTypeReference outputType = GetTypeReference(type, false);

                    CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement();

                    CodeMethodInvokeExpression convertArrayMethod = new CodeMethodInvokeExpression();
                    convertArrayMethod.Method = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_CONVERT_ARRAY);
                    convertArrayMethod.Method.TypeArguments.Add(outputType);

                    CodeMethodInvokeExpression appSettingSplit = new CodeMethodInvokeExpression(GetConfigurationManagerAppSettingsExpression(key), METHOD_STRING_SPLIT, new CodePrimitiveExpression(separator));
                    convertArrayMethod.Parameters.Add(appSettingSplit);

                    returnStatement.Expression = convertArrayMethod;

                    getStatements.Add(returnStatement);
                }
                // It hasn't a defined type
                else
                {
                    // Code: return ConfigurationManager.AppSettings[appSettingsName].Split(separator);
                    getStatements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(GetConfigurationManagerAppSettingsExpression(key), METHOD_STRING_SPLIT, new CodePrimitiveExpression(separator))));
                }
            }
            // If the appSettings has the default attribute
            else
            {
                getStatements = CreateAppSettingPropertyGetStatementsArrayWithDefault(key, type, separator, appSettingDefault, ownerClassName);
            }

            return getStatements;
        }

        /// <summary>
        /// Creates the app setting property get statements array with default.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="type">The type.</param>
        /// <param name="appSettingDefault">The app setting default.</param>
        /// <returns></returns>
        private static List<CodeStatement> CreateAppSettingPropertyGetStatementsArrayWithDefault(String key, String type, Char separator, String appSettingDefault, String ownerClassName)
        {
            List<CodeStatement> getStatements = new List<CodeStatement>();

            String appSettingVariableName = "appSetting";
            // and a defined type
            if (type != null && type != "string")
            {
                includeConvertArray = true;
                CodeTypeReference outputType = GetTypeReference(type, false);
                /* Code: 
                 * String appSetting = ConfigurationManager.AppSettings[appSettingsName];
                 * if(String.IsNullOrEmpty(appSetting)
                 * {
                 *     return new definedType[0]; or return ConvertArray<definedType>("definedDefault".Split(separator));
                 * }
                 * else
                 * {
                 *     return ConvertArray<definedType>(appSetting.Split(separator));
                 * }
                 * */
                getStatements.Add(new CodeVariableDeclarationStatement(TYPE_STRING, appSettingVariableName, GetConfigurationManagerAppSettingsExpression(key)));
                
                CodeMethodInvokeExpression convertArrayMethod = new CodeMethodInvokeExpression();
                    convertArrayMethod.Method = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_CONVERT_ARRAY);
                    convertArrayMethod.Method.TypeArguments.Add(outputType);
                    convertArrayMethod.Parameters.Add(new CodeMethodInvokeExpression(new CodePrimitiveExpression(appSettingDefault), METHOD_STRING_SPLIT, new CodePrimitiveExpression(separator)));

                    CodeMethodInvokeExpression convertArrayMethod2 = new CodeMethodInvokeExpression();
                    convertArrayMethod2.Method = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_CONVERT_ARRAY);
                    convertArrayMethod2.Method.TypeArguments.Add(outputType);
                    convertArrayMethod2.Parameters.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(appSettingVariableName), METHOD_STRING_SPLIT, new CodePrimitiveExpression(separator)));

                getStatements.Add(new CodeConditionStatement(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_STRING), METHOD_STRING_ISNULLOREMPTY, new CodeVariableReferenceExpression(appSettingVariableName))
                                                ,
                                            new CodeStatement[] 
                                            { 
                                                ((appSettingDefault == String.Empty)?
                                                    new CodeMethodReturnStatement(new CodeArrayCreateExpression(outputType, 0))
                                                :
                                                    new CodeMethodReturnStatement(convertArrayMethod) 
                                                )
                                            }
                                                ,
                                            new CodeStatement[] 
                                                { 
                                                    new CodeMethodReturnStatement(convertArrayMethod2)
                                                }
                                                ));
            }
            // and hasn't a defined type
            else
            {
                /* Code: 
                 * String appSetting = ConfigurationManager.AppSettings[appSettingsName];
                 * if(String.IsNullOrEmpty(appSetting)
                 * {
                 *     return new String[0]; or return "definedDefault".Split(separator);
                 * }
                 * else
                 * {
                 *     return appSetting.Split(separator);
                 * }
                 * */
                getStatements.Add(new CodeVariableDeclarationStatement(TYPE_STRING, appSettingVariableName, GetConfigurationManagerAppSettingsExpression(key)));

                getStatements.Add(new CodeConditionStatement(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_STRING), METHOD_STRING_ISNULLOREMPTY, new CodeVariableReferenceExpression(appSettingVariableName))
                                                ,
                                            new CodeStatement[] 
                                            { 
                                                ((appSettingDefault == String.Empty)?
                                                    new CodeMethodReturnStatement(new CodeArrayCreateExpression(TYPE_STRING_ARRAY, 0))
                                                :
                                                    new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodePrimitiveExpression(appSettingDefault), METHOD_STRING_SPLIT, new CodePrimitiveExpression(separator))) 
                                                )
                                            }
                                                ,
                                            new CodeStatement[] { new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(appSettingVariableName), METHOD_STRING_SPLIT, new CodePrimitiveExpression(separator))) }
                                                ));
            }

            return getStatements;
        }
        /// <summary>
        /// Gets the configuration manager app settings.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        private static CodeExpression GetConfigurationManagerAppSettingsExpression(String key)
        {
            return new CodeIndexerExpression(new CodePropertyReferenceExpression(
                                                    new CodeTypeReferenceExpression(TYPE_CONFIGURATIONMANAGER), PROPERTY_CONFIGURATIONMANAGER_APPSETTINGS), new CodePrimitiveExpression(key));
        }

        /// <summary>
        /// Creates the connection string property.
        /// </summary>
        /// <param name="xmlConnectionString">The XML connection string.</param>
        /// <returns></returns>
        private static CodeMemberProperty CreateConnectionStringProperty(XElement xmlConnectionString)
        {
            CodeMemberProperty connectionStringProperty = new CodeMemberProperty();

            String name = xmlConnectionString.Attribute("name").Value;

            connectionStringProperty.AddDocComment("<summary>"
                                                    , "Gets the ConnectionString " + name
                                                    , "</summary>"
                                                    , "<value>"
                                                    , "The " + name + " connectionString"
                                                    , "</value>");

            connectionStringProperty.Name = name[0].ToString().ToUpper();
            for (int i = 1; i < name.Length; i++)
            {
                connectionStringProperty.Name += name[i];
            }

            connectionStringProperty.Type = TYPE_CONNECTIONSTRINGSETTINGS;
            connectionStringProperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;

            connectionStringProperty.HasGet = true;
            connectionStringProperty.HasSet = false;

            // Code: return ConfigurationManager.ConnectionStrings[name];
            connectionStringProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeIndexerExpression(PROPERTY_CONFIGURATIONMANAGER_CONNECTIONSTRINGS, new CodePrimitiveExpression(name))));

            return connectionStringProperty;
        }

        /// <summary>
        /// Creates the convert array method.
        /// </summary>
        /// <returns></returns>
        private static CodeMemberMethod CreateConvertArrayMethod()
        {
            CodeMemberMethod convertArrayMethod = new CodeMemberMethod();
            convertArrayMethod.Name = METHOD_GENERATEDCONFIGURATION_CONVERT_ARRAY;
            convertArrayMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
            convertArrayMethod.TypeParameters.Add(new CodeTypeParameter("T"));
            convertArrayMethod.AddDocComment("<summary>"
                                    , "Convert a string array into a defined type array"
                                    , "</summary>"
                                    , "<param name=\"stringArray\">The string array to convert.</param>",
                                    "<returns>The converted array</returns>");
            convertArrayMethod.ReturnType = TYPE_T_ARRAY;

            String stringArrayName = "stringArray";
            String typedArrayName = "typedArray";

            convertArrayMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_STRING_ARRAY, stringArrayName));

            CodeTypeReference outputType = TYPE_T_ARRAY;
            CodeVariableReferenceExpression variableI = new CodeVariableReferenceExpression("i");

            convertArrayMethod.Statements.Add(new CodeVariableDeclarationStatement(outputType, typedArrayName, new CodeArrayCreateExpression(outputType, new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(stringArrayName), PROPERTY_ARRAY_LENGTH))));

            convertArrayMethod.Statements.Add(new CodeIterationStatement(
                // Initialization                    
                new CodeVariableDeclarationStatement(TYPE_INT32, "i", new CodePrimitiveExpression(0)),
                // Test
                new CodeBinaryOperatorExpression(variableI,
                        CodeBinaryOperatorType.LessThan, new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(stringArrayName), PROPERTY_ARRAY_LENGTH)),
                // Incrementation
                new CodeAssignStatement(variableI, new CodeBinaryOperatorExpression(
                    variableI, CodeBinaryOperatorType.Add, new CodePrimitiveExpression(1))),
                // Statement
                new CodeAssignStatement(new CodeIndexerExpression(new CodeVariableReferenceExpression(typedArrayName), variableI),
                    new CodeCastExpression(TYPE_T, new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_CHANGETYPE, new CodeIndexerExpression(new CodeVariableReferenceExpression(stringArrayName), variableI), new CodeTypeOfExpression(TYPE_T))))
            ));

            convertArrayMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression(typedArrayName)));
            
            return convertArrayMethod;
        }

        /// <summary>
        /// Creates the save method.
        /// </summary>
        /// <returns></returns>
        private static CodeMemberMethod CreateSaveMethod(String ownerClassName)
        {
            includeSaveAppSetting = true;

            CodeMemberMethod saveMethod = new CodeMemberMethod();
            saveMethod.Name = METHOD_GENERATEDCONFIGURATION_SAVE;
            saveMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;

            saveMethod.AddDocComment("<summary>"
                                    , "Save the configuration"
                                    , "</summary>"
                                    , "<param name=\"propertyName\">The property name.</param>"
                                    , "<param name=\"propertyValue\">The property value.</param>");

            // Set parameters and variable names
            String propertyNameName = "propertyName";
            String propertyValueName = "propertyValue";

            // Add parameters
            saveMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_STRING, propertyNameName));
            saveMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_OBJECT, propertyValueName));

            // Set variable references
            CodeVariableReferenceExpression cvrePropertyName = new CodeVariableReferenceExpression(propertyNameName);
            CodeVariableReferenceExpression cvrePropertyValue = new CodeVariableReferenceExpression(propertyValueName);

            // Code: SaveAppSetting(propertyName, propertyValue.ToString()
            saveMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_SAVE_APPSETTING, cvrePropertyName, new CodeMethodInvokeExpression(cvrePropertyValue, METHOD_OBJECT_TOSTRING)));

            return saveMethod;
        }

        /// <summary>
        /// Creates the save method.
        /// </summary>
        /// <returns></returns>
        private static CodeMemberMethod CreateSaveIEnumarableMethod(String ownerClassName)
        {
            includeSaveAppSetting = true;

            CodeMemberMethod saveMethod = new CodeMemberMethod();
            saveMethod.Name = METHOD_GENERATEDCONFIGURATION_SAVE;
            saveMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
            saveMethod.TypeParameters.Add(new CodeTypeParameter("T"));
            saveMethod.AddDocComment("<summary>"
                                    , "Save the configuration"
                                    , "</summary>"
                                    , "<param name=\"propertyName\">The property name.</param>"
                                    , "<param name=\"propertyValue\">The property value.</param>"
                                    , "<param name=\"separator\">The separator used.</param>");

            // Set parameters and variable names
            String propertyNameName = "propertyName";
            String propertyValueName = "propertyValue";
            String stringValueName = "stringValue";
            String separatorName = "separator";

            // Add parameters
            saveMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_STRING, propertyNameName));
            CodeTypeReference iEnumerableType = new CodeTypeReference("System.Collections.Generic.IEnumerable");
            iEnumerableType.TypeArguments.Add(TYPE_T);
            saveMethod.Parameters.Add(new CodeParameterDeclarationExpression(iEnumerableType, propertyValueName));
            saveMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_CHAR, separatorName));

            // Set variable references
            CodeVariableReferenceExpression cvrePropertyName = new CodeVariableReferenceExpression(propertyNameName);
            CodeVariableReferenceExpression cvrePropertyValue = new CodeVariableReferenceExpression(propertyValueName);
            CodeVariableReferenceExpression cvreStringValue = new CodeVariableReferenceExpression(stringValueName);
            CodeVariableReferenceExpression cvreSeparator = new CodeVariableReferenceExpression(separatorName);

            /* Code: 
             * String stringValue = String.Empty;
             * for(IEnumerator it = propertyValue.GetEnumerator(); it.MoveNext();)
             * {
             *      stringValue += it.Current.ToString() + separator;
             * }
             * stringValue = stringValue.Remove(stringValue.Length - 1, 1);
             * SaveAppSetting(propertyName, stringValue);
             */
            // String stringValue = String.Empty;
            saveMethod.Statements.Add(new CodeVariableDeclarationStatement(TYPE_STRING, stringValueName,FIELD_STRING_EMPTY));
            
            CodeVariableReferenceExpression variableIt = new CodeVariableReferenceExpression("it");

            // For
            saveMethod.Statements.Add(new CodeIterationStatement(
                // Initialization                    
                // IEnumerator it = propertyValue.GetEnumerator()
                new CodeVariableDeclarationStatement(TYPE_IENUMERATOR, "it", new CodeMethodInvokeExpression(cvrePropertyValue, METHOD_IENUMERATOR_GETENUMERATOR)),
                // Test
                // it.MoveNext()
                new CodeMethodInvokeExpression(variableIt, METHOD_IENUMERATOR_MOVENEXT),
                // Incrementation
                // nothing
                new CodeSnippetStatement(),
                
                // Statement
                // stringValue = 
                new CodeAssignStatement(cvreStringValue,
                    // stringValue + 
                    new CodeBinaryOperatorExpression(cvreStringValue, CodeBinaryOperatorType.Add,
                    // it.Current.ToString() + separator;
                       new CodeBinaryOperatorExpression(new CodeMethodInvokeExpression(new CodePropertyReferenceExpression(variableIt, PROPERTY_IENUMERABLE_CURRENT), METHOD_OBJECT_TOSTRING), CodeBinaryOperatorType.Add, cvreSeparator))))
            );

            // stringValue = stringValue.Remove(stringValue.Length - 1, 1);
            saveMethod.Statements.Add(new CodeAssignStatement(cvreStringValue, new CodeMethodInvokeExpression(cvreStringValue, METHOD_STRING_REMOVE, new CodeBinaryOperatorExpression(new CodePropertyReferenceExpression(cvreStringValue, PROPERTY_ARRAY_LENGTH), CodeBinaryOperatorType.Subtract, new CodePrimitiveExpression(1)), new CodePrimitiveExpression(1))));

            // SaveAppSetting(propertyName, stringValue);
            saveMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(ownerClassName), METHOD_GENERATEDCONFIGURATION_SAVE_APPSETTING, cvrePropertyName, cvreStringValue));

            return saveMethod;
        }

        /// <summary>
        /// Creates the save app setting method.
        /// </summary>
        /// <returns></returns>
        private static CodeMemberMethod CreateSaveAppSettingMethod()
        {
            CodeMemberMethod saveAppSettingMethod = new CodeMemberMethod();
            saveAppSettingMethod.Name = METHOD_GENERATEDCONFIGURATION_SAVE_APPSETTING;
            saveAppSettingMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;

            saveAppSettingMethod.AddDocComment("<summary>"
                                    , "Save the appSetting"
                                    , "</summary>"
                                    , "<param name=\"propertyName\">The property name.</param>"
                                    , "<param name=\"propertyValue\">The property value.</param>");

            // Set parameters and variable names
            String propertyNameName = "propertyName";
            String propertyValueName = "propertyValue";
            String configurationName = "configuration";

            // Add parameters
            saveAppSettingMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_STRING, propertyNameName));
            saveAppSettingMethod.Parameters.Add(new CodeParameterDeclarationExpression(TYPE_STRING, propertyValueName));

            // Set variable references
            CodeVariableReferenceExpression cvreConfiguration = new CodeVariableReferenceExpression(configurationName);
            CodeVariableReferenceExpression cvrePropertyName = new CodeVariableReferenceExpression(propertyNameName);
            CodeVariableReferenceExpression cvrePropertyValue = new CodeVariableReferenceExpression(propertyValueName);

            // Code: Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationLevel.None)
            saveAppSettingMethod.Statements.Add(new CodeVariableDeclarationStatement(TYPE_CONFIGURATION, configurationName, new CodeMethodInvokeExpression(
                                                new CodeTypeReferenceExpression(TYPE_CONFIGURATIONMANAGER), METHOD_CONFIGURATIONMANAGER_OPENEXECONFIGURATION, PROPERTY_CONFIGURATIONUSERLEVEL_NONE)));

            // Code: configuration.AppSettings.Settings[propertyName].Value = propertyValue
            saveAppSettingMethod.Statements.Add(new CodeAssignStatement(
                                            new CodePropertyReferenceExpression(
                                                    new CodeIndexerExpression(
                                                            new CodePropertyReferenceExpression(
                                                                    new CodePropertyReferenceExpression(
                                                                            cvreConfiguration,
                                                                            PROPERTY_CONFIGURATIONMANAGER_APPSETTINGS),
                                                                    PROPERTY_APPSETTINGS_SETTINGS),
                                                            cvrePropertyName),
                                                    PROPERTY_SETTINGS_VALUE),
                                                    cvrePropertyValue));

            // Code: configuration.Save(ConfigurationSaveMode.Modified)
            saveAppSettingMethod.Statements.Add(new CodeMethodInvokeExpression(cvreConfiguration, METHOD_CONFIGURATION_SAVE, PROPERTY_CONFIGURATIONSAVEMODE_MODIFIED));

            // Code: ConfigurationManager.RefreshSection("appSettings")
            saveAppSettingMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONFIGURATIONMANAGER), METHOD_CONFIGURATIONMANAGER_REFRESHSECTION, new CodePrimitiveExpression("appSettings")));

            return saveAppSettingMethod;
        }

        /// <summary>
        /// Gets the provider.
        /// </summary>
        /// <param name="language">The language.</param>
        /// <returns></returns>
        private static CodeDomProvider GetProvider(AvailableLanguage language)
        {
            CodeDomProvider provider;
            switch (language)
            {
                case AvailableLanguage.CSharp:
                    provider = CodeDomProvider.CreateProvider("CSharp");
                    break;
                case AvailableLanguage.VisualBasic:
                    provider = CodeDomProvider.CreateProvider("VisualBasic");
                    break;
                case AvailableLanguage.JScript:
                    provider = CodeDomProvider.CreateProvider("JScript");
                    break;
                default:
                    provider = CodeDomProvider.CreateProvider("CSharp");
                    break;
            }
            return provider;
        }

        /// <summary>
        /// Gets the convert to method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        private static CodeMethodInvokeExpression GetConvertToMethod(String type, CodeExpression parameter)
        {
            CodeMethodInvokeExpression convertToMethod = null;
            switch (type.ToLower())
            {
                case "int": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TOINT32, parameter);
                    break;

                case "bool": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TOBOOLEAN, parameter);
                    break;

                case "decimal": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TODECIMAL, parameter);
                    break;

                case "float": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TOSINGLE, parameter);
                    break;

                case "char": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TOCHAR, parameter);
                    break;

                case "datetime": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TODATETIME, parameter);
                    break;

                case "double": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TODOUBLE, parameter);
                    break;

                case "uint": convertToMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(TYPE_CONVERT), METHOD_CONVERT_TOUINT32, parameter);
                    break;

                default: convertToMethod = new CodeMethodInvokeExpression(parameter, METHOD_OBJECT_TOSTRING);
                    break;
            }


            return convertToMethod;
        }

        /// <summary>
        /// Gets the type reference.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="isArray">if set to <c>true</c> [is array].</param>
        /// <returns></returns>
        private static CodeTypeReference GetTypeReference(String type, Boolean isArray)
        {
            CodeTypeReference typeReference = null;

            if (isArray)
            {
                switch (type.ToLower())
                {
                    case "int": typeReference = TYPE_INT32_ARRAY;
                        break;

                    case "bool": typeReference = TYPE_BOOLEAN_ARRAY;
                        break;

                    case "decimal": typeReference = TYPE_DECIMAL_ARRAY;
                        break;

                    case "float": typeReference = TYPE_SINGLE_ARRAY;
                        break;

                    case "char": typeReference = TYPE_CHAR_ARRAY;
                        break;

                    case "datetime": typeReference = TYPE_DATETIME_ARRAY;
                        break;

                    case "double": typeReference = TYPE_DOUBLE_ARRAY;
                        break;

                    case "uint": typeReference = TYPE_UINT32_ARRAY;
                        break;

                    default: typeReference = TYPE_STRING_ARRAY;
                        break;
                }
            }
            else
            {
                switch (type.ToLower())
                {
                    case "int": typeReference = TYPE_INT32;
                        break;

                    case "bool": typeReference = TYPE_BOOLEAN;
                        break;

                    case "decimal": typeReference = TYPE_DECIMAL;
                        break;

                    case "float": typeReference = TYPE_SINGLE;
                        break;

                    case "char": typeReference = TYPE_CHAR;
                        break;

                    case "datetime": typeReference = TYPE_DATETIME;
                        break;

                    case "double": typeReference = TYPE_DOUBLE;
                        break;

                    case "uint": typeReference = TYPE_UINT32;
                        break;

                    default: typeReference = TYPE_STRING;
                        break;
                }
            }

            return typeReference;
        }

        /// <summary>
        /// Generates the code.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="compileunit">The compileunit.</param>
        /// <param name="applicationName">Name of the application.</param>
        private static String GenerateCode(CodeDomProvider provider, CodeCompileUnit compileunit, String applicationName)
        {
            StringWriter stringWriter = new StringWriter();
            provider.GenerateCodeFromCompileUnit(compileunit, stringWriter, new CodeGeneratorOptions() { BracingStyle = "C" });
            return stringWriter.ToString();
        }

        #endregion Private methods


        #region Nested enum

        /// <summary>
        /// Aailable languages
        /// </summary>
        public enum AvailableLanguage
        {
            /// <summary>
            /// C#
            /// </summary>
            CSharp,
            /// <summary>
            /// Visual Basic
            /// </summary>
            VisualBasic,
            /// <summary>
            /// JScript
            /// </summary>
            JScript,
        }

        #endregion Nested enum
    }
}
